﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace TelerikMvcGridCustomBindingHelper.Mapper
{
    public class BooleanSubstitutes : ConcurrentDictionary<BooleanSubstituteKey, IBooleanSubstitute>
    {
        public bool HasSubstituteFor<TEntity, TViewModel>(GridPropertyInfo gridPropertyInfo)
        {
            return ContainsKey(BooleanSubstituteKey.New(typeof(TEntity), typeof(TViewModel), gridPropertyInfo))
                   || ContainsKey(BooleanSubstituteKey.GlobalKey());
        }

        public IEnumerable<object> GetSubstitutesFor<TEntity, TViewModel>(GridPropertyInfo gridPropertyInfo, bool? value)
        {
            IBooleanSubstitute booleanSubstitute;

            var tryGetValue = TryGetValue(BooleanSubstituteKey.New(typeof(TEntity), typeof(TViewModel), gridPropertyInfo), out booleanSubstitute);

            if (tryGetValue == false)
                tryGetValue = TryGetValue(BooleanSubstituteKey.GlobalKey(), out booleanSubstitute);

            if (tryGetValue == false)
                return null;

            if (value == null)
            {
                var nullMeansTrue = booleanSubstitute.TrueValues().Any(o => o == null);
                if (nullMeansTrue)
                {
                    return booleanSubstitute.TrueValues();
                }

                var nullMeansFalse = booleanSubstitute.FalseValues().Any(o => o == null);
                if (nullMeansFalse)
                {
                    return booleanSubstitute.FalseValues();
                }
            }

            return value != null && (bool)value ? booleanSubstitute.TrueValues() : booleanSubstitute.FalseValues();
        }
    }

    public struct BooleanSubstituteKey : IEquatable<BooleanSubstituteKey>
    {
        public readonly Type EntityType;
        public readonly Type ViewModelType;
        public readonly GridPropertyInfo GridPropertyInfo;

        private BooleanSubstituteKey(Type entityType, Type viewModelType, GridPropertyInfo gridPropertyInfo)
        {
            EntityType = entityType;
            ViewModelType = viewModelType;
            GridPropertyInfo = gridPropertyInfo;
        }

        public static BooleanSubstituteKey New(Type entityType, Type viewModelType, GridPropertyInfo gridPropertyInfo)
        {
            return new BooleanSubstituteKey(entityType, viewModelType, gridPropertyInfo);
        }

        public static BooleanSubstituteKey GlobalKey()
        {
            var type = typeof(IBooleanSubstitute);
            var gpi = new GridPropertyInfo(type, type.FullName);
            return new BooleanSubstituteKey(type, type, gpi);
        }

        #region IEquatable<BooleanSubstituteKey>

        public bool Equals(BooleanSubstituteKey other)
        {
            return other.EntityType == EntityType
                   && other.ViewModelType == ViewModelType
                   && GridPropertyInfo != null
                   && (other.GridPropertyInfo.PropertyType == GridPropertyInfo.PropertyType
                   && Equals(other.GridPropertyInfo.PropertyPath, GridPropertyInfo.PropertyPath));
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof(BooleanSubstituteKey)) return false;
            return Equals((BooleanSubstituteKey)obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                var result = ToString().GetHashCode();

                if (EntityType != null) result = (result * 397) ^ EntityType.GetHashCode();

                if (ViewModelType != null) result = (result * 397) ^ ViewModelType.GetHashCode();

                if (GridPropertyInfo != null)
                {
                    if (GridPropertyInfo.PropertyPath != null)
                        result = (result * 397) ^ GridPropertyInfo.PropertyPath.GetHashCode();
                    if (GridPropertyInfo.PropertyType != null)
                        result = (result * 397) ^ GridPropertyInfo.PropertyType.GetHashCode();
                }

                return result;
            }
        }

        #endregion
    }
}